package com.ssca.analyse.smali.bound.parser;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.ssca.analyse.smali.controlflow.FunctionData;
import com.ssca.analyse.smali.controlflow.OredredMethod;
import com.ssca.commonData.CommonData;
import com.ssca.commonData.IntentData;

public class UriMatcherAddURI {

	private static Logger logger = LogManager.getLogger();

	private List<IntentData> intentDataList = new ArrayList<IntentData>();

	@SuppressWarnings("unused")
	private FunctionData funData;

	private String api;

	private IntentData intentData;

	private Map<Integer, OredredMethod> Method_Lines;

	private Map<Integer, List<String>> registerMessage;

	private Map<Integer, List<Integer>> registerLast;

	@SuppressWarnings("unused")
	private Map<String, List<Integer>> registerCall;

	public UriMatcherAddURI(FunctionData fundata) {
		// 根据传入的FunctionData获取控制流和数据流
		this.funData = fundata;
		Method_Lines = fundata.getMethod_Line();
		registerMessage = fundata.getRegisterMessage();
		registerLast = fundata.getRegisterLast();
		registerCall = fundata.getRegisterCall();
	}

	/**
	 * smali解析：针对smali文件中method区域的数据（FunctionData）指定api，解析uri.parse参数
	 */
	public List<IntentData> uriParse(String api) {
		this.api = api;
		analyseSmaliFunData();
		return intentDataList;
	}

	private void analyseSmaliFunData() {
		for (Map.Entry<Integer, OredredMethod> LineEntry : Method_Lines.entrySet()) {
			int lineNumber = LineEntry.getKey();// 该行行号
			OredredMethod line = LineEntry.getValue();
			if (line.getStatementStr().contains(api)) {// 匹配关键api的行
				intentData = new IntentData();
				intentData.setSourcePkg(CommonData.getApkPkg());
				intentData.setCallApi(api);
				intentData.setStartActivityLine(lineNumber);

				// 获取startActivity调用者信息
				// intentData.setCaller(line.getStatements().getChild(2).toString());

				String caller = getCallerInfo(line.getRegister().get(0), lineNumber, line);
				intentData.setCaller(caller);

				// 获取startActivity()中的参数intent的寄存器
				List<String> addURIParams = registerMessage.get(lineNumber);
				if (addURIParams == null || addURIParams.size() == 0)
					return;
				String uriRegister = registerMessage.get(lineNumber).get(0);
				getUriParams(uriRegister, intentData.getStartActivityLine());

				intentDataList.add(intentData);
			}
		}
	}

	/**
	 * @param lineNumber
	 *            搜索的起始行数
	 * @param Register
	 *            需要搜索的寄存器名字
	 * @return 从lineNumber开始向上追踪最近一次出现寄存器Register的位置
	 * 
	 */
	private int getLastLine(int lineNumber, String Register) {
		Queue<Integer> queue = new ArrayDeque<Integer>();
		queue.add(lineNumber);
		while (!queue.isEmpty()) {
			int line = queue.remove();
			if (Method_Lines.get(line).register.size() > 0 && Method_Lines.get(line).register.get(0).equals(Register)
					&& line != lineNumber && line != intentData.getStartActivityLine()) {
				if (line < intentData.getStartActivityLine())
					return line;
			}
			List<Integer> LastLineNumberList = registerLast.get(line);
			if (LastLineNumberList == null || LastLineNumberList.size() == 0)
				break;
			for (int lastLine : LastLineNumberList) {
				if (lastLine != lineNumber && lastLine < lineNumber) {
					queue.add(lastLine);
				}
			}
		}
		return -1;
	}

	/**
	 * @param uriRegister
	 *            URI的寄存器
	 * @param uriLine
	 *            addURI的位置
	 * @return 获取URI
	 */
	private void getUriParams(String uriRegister, int uriLine) {

		// 从uriLine开始往上搜，获取addURI的URI的定义位置：const-string v*
		int lineTemp = getLastLine(uriLine, uriRegister);
		OredredMethod oredredMethodTemp = Method_Lines.get(lineTemp);
		while (lineTemp != -1) {
			oredredMethodTemp = Method_Lines.get(lineTemp);
			if (oredredMethodTemp.getOpcode().equals("const-string")
					&& oredredMethodTemp.getRegister().get(0).equals(uriRegister)) {
				logger.info("get const URI at:" + lineTemp);
				intentData.setInitLine(lineTemp);
				intentData.setHost(oredredMethodTemp.statements.getChild(2).getText());
				intentData.setScheme("content");
				intentData.setUri("content://" + oredredMethodTemp.statements.getChild(2).getText());
				break;
			} else {
				lineTemp = getLastLine(lineTemp, uriRegister);
			}
		}
		if (intentData.getInitLine() == -1) {
			logger.info("can not found URI const-string");
			return;
		}

	}

	/**
	 * @param registerName
	 *            追踪的寄存器的名字
	 * @param cur_line
	 *            当前行
	 * @param limitLine
	 *            往上追踪的截止的截止行
	 * @return 返回追踪到的定值const
	 */
	@SuppressWarnings("unused")
	private String getConstValue(String registerName, int cur_line, int limitLine) {
		int lastLine = getLastLine(cur_line, registerName);
		// 16-04-21: 调整为上一个指令如果不是const 则放弃追踪 return空
		OredredMethod lineInfo = Method_Lines.get(lastLine);
		if (lineInfo != null) {
			if (lineInfo.getOpcode().equals("const-string")) {
				String reslut = lineInfo.getStatementStr().split(registerName)[1].trim();
				return reslut.substring(1, reslut.length() - 1);
			} else if (lineInfo.getOpcode().contains("const")) {
				return lineInfo.getStatementStr().split(registerName)[1];
			}
		}
		return "";
	}

	private String getCallerInfo(String contextRegister, int startLine, OredredMethod line) {
		String caller = "Landroid/content/Context;";
		String curInfo = line.getStatements().getChild(2).toString();
		if ((!curInfo.contains("Landroid/content/Context;")) && (!curInfo.contains("Landroid/content/UriMatcher"))) {
			return curInfo;
		} else {
			int lastLine = getLastLine(startLine, contextRegister);
			if (lastLine != -1) {
				OredredMethod lastLineInfo = Method_Lines.get(lastLine);
				String lastLineOp = lastLineInfo.getOpcode();
				if (lastLineOp.equals("sget-object")) {
					caller = lastLineInfo.getStatements().getChild(2).toString();
				} else if (lastLineOp.equals("move-result-object")) {
					// 修改追踪的方法，不用getLastLine，直接通过行数向上找
					while (Method_Lines.get(--lastLine) == null)
						;
					if (Method_Lines.get(lastLine).getOpcode().startsWith("invoke")) {
						caller = Method_Lines.get(lastLine).getStatements().getChild(2).toString();
					}
				}
			}
			return caller;
		}
	}
}
